package com.bjy.qa.agent.mock.http;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONValidator;
import com.bjy.qa.agent.tools.json.JsonHelper;
import net.minidev.json.JSONArray;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;

import javax.servlet.ServletInputStream;
import javax.servlet.http.Cookie;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.*;

public class MockRequest {
    private MockContentType contentType; // 请求的 Content-Type
    private MockMethod Method; // 请求的方法
    private String url; // 请求的 URL
    private String uri; // 请求的 URI
    private String scheme; // 请求的 scheme
    private int localPort; // 请求的 localPort（应用服务器的端口。如果你请求的是 8080 但经过 nginx 转发到 80，那这里获得的是 80）
    private int serverPort; // 请求的 serverPort（请求URL中的端口。如果你请求的是 8080 但经过 nginx 转发到 80，那这里获得的是 8080）
    private int remotePort; // 请求的 remotePort（请求的客户端的端口）
    private String protocol; // 请求的 protocol（请求的协议，如 HTTP/1.1）
    private String host; // 请求的 host（请求的域名或 IP）
    private String remoteHost; // 请求的 remoteHost（请求的客户端的域名或 IP）
    private String remoteAddr; // 请求的 remoteAddr（请求的客户端的 IP）
    private Map<String, String> headers = new HashMap<>(); // 请求的 headers
    private Map<String, String> cookies = new HashMap<>(); // 请求的 cookies
    private Map<String, ArrayList<String>> params = new HashMap<>(); // 请求的 url 中的参数（queryString 请求的查询字符串）
    private Object body = null; // 请求的 body 参数

    public MockRequest(BufferedHttpServletRequestWrapper request) {
        this.setContentType(request.getHeader("Content-Type")); // 获取请求的 Content-Type
        this.setMethod(request.getMethod()); // 获取请求的方法
        this.url = request.getRequestURL().toString(); // 获取请求的 URL
        this.uri = request.getRequestURI().replaceAll("/+/", "/"); // 获取请求的 URI
        this.scheme = request.getScheme();
        this.localPort = request.getLocalPort();
        this.serverPort = request.getServerPort();
        this.remotePort = request.getRemotePort();
        this.protocol = request.getProtocol();
        this.host = request.getHeader("Host");
        this.remoteHost = request.getRemoteHost();
        this.remoteAddr = request.getRemoteAddr();
        this.setHeaders(request);
        this.setCookies(request);
        this.setParams(request.getQueryString());
        this.setBody(request);
    }

    /**
     * 获取请求的 Content-Type
     * @param contentType
     */
    private void setContentType(String contentType) {
        if (null == contentType) {
            this.contentType = MockContentType.NONE;
        } else if (contentType.toLowerCase().indexOf("multipart/form-data;") == 0) {
            this.contentType = MockContentType.FORM_DATA;
        } else if ("application/x-www-form-urlencoded".equalsIgnoreCase(contentType)) {
            this.contentType = MockContentType.X_WWW_FORM_URLENCODED;
        } else if ("text/plain".equalsIgnoreCase(contentType)) {
            this.contentType = MockContentType.TEXT;
        } else if ("application/javascript".equalsIgnoreCase(contentType)) {
            this.contentType = MockContentType.JAVASCRIPT;
        } else if ("application/json".equalsIgnoreCase(contentType)) {
            this.contentType = MockContentType.JSON;
        } else if ("text/html".equalsIgnoreCase(contentType)) {
            this.contentType = MockContentType.HTML;
        } else if ("application/xml".equalsIgnoreCase(contentType)) {
            this.contentType = MockContentType.XML;
        } else {
            this.contentType = MockContentType.UNKNOWN;
        }
    }

    /**
     * 返回请求的 Content-Type
     * @return
     */
    public MockContentType getContentType() {
        return contentType;
    }

    /**
     * 获取请求的方法
     * @param method
     */
    private void setMethod(String method) {
        if ("GET".equalsIgnoreCase(method)) {
            this.Method = MockMethod.GET;
        } else if ("POST".equalsIgnoreCase(method)) {
            this.Method = MockMethod.POST;
        } else if ("PUT".equalsIgnoreCase(method)) {
            this.Method = MockMethod.PUT;
        } else if ("PATCH".equalsIgnoreCase(method)) {
            this.Method = MockMethod.PATCH;
        } else if ("DELETE".equalsIgnoreCase(method)) {
            this.Method = MockMethod.DELETE;
        } else if ("COPY".equalsIgnoreCase(method)) {
            this.Method = MockMethod.COPY;
        } else if ("HEAD".equalsIgnoreCase(method)) {
            this.Method = MockMethod.HEAD;
        } else if ("OPTIONS".equalsIgnoreCase(method)) {
            this.Method = MockMethod.OPTIONS;
        } else if ("LINK".equalsIgnoreCase(method)) {
            this.Method = MockMethod.LINK;
        } else if ("UNLINK".equalsIgnoreCase(method)) {
            this.Method = MockMethod.UNLINK;
        } else if ("PURGE".equalsIgnoreCase(method)) {
            this.Method = MockMethod.PURGE;
        } else if ("LOCK".equalsIgnoreCase(method)) {
            this.Method = MockMethod.LOCK;
        } else if ("UNLOCK".equalsIgnoreCase(method)) {
            this.Method = MockMethod.UNLOCK;
        } else if ("PROPFIND".equalsIgnoreCase(method)) {
            this.Method = MockMethod.PROPFIND;
        } else if ("VIEW".equalsIgnoreCase(method)) {
            this.Method = MockMethod.VIEW;
        } else {
            this.Method = MockMethod.UNKNOWN;
        }
    }

    /**
     * 返回请求的方法
     * @return
     */
    public MockMethod getMethod() {
        return Method;
    }

    /**
     * 返回请求的 URL。例如：http://localhost:8080/test
     * @return
     */
    public String getUrl() {
        return url;
    }

    /**
     * 返回请求的 URI。例如：/test
     * @return
     */
    public String getUri() {
        return uri;
    }

    /**
     * 返回请求的 scheme。例如：http
     * @return
     */
    public String getScheme() {
        return scheme;
    }

    /**
     * 返回请求的 localPort（应用服务器的端口。如果你请求的是 8080 但经过 nginx 转发到 80，那这里获得的是 80）
     * @return
     */
    public int getLocalPort() {
        return localPort;
    }

    /**
     * 返回请求的 serverPort（请求URL中的端口。如果你请求的是 8080 但经过 nginx 转发到 80，那这里获得的是 8080）
     * @return
     */
    public int getServerPort() {
        return serverPort;
    }

    /**
     * 返回请求的 remotePort（请求的客户端的端口）
     * @return
     */
    public int getRemotePort() {
        return remotePort;
    }

    /**
     * 返回请求的 protocol。例如：HTTP/1.1
     * @return
     */
    public String getProtocol() {
        return protocol;
    }

    /**
     * 返回请求的 host。例如：localhost:8080
     * @return
     */
    public String getHost() {
        return host;
    }

    /**
     * 返回请求的 remoteHost（请求的客户端的域名或 IP）
     * @return
     */
    public String getRemoteHost() {
        return remoteHost;
    }

    /**
     * 返回请求的 remoteAddr（请求的客户端的 IP）
     * @return
     */
    public String getRemoteAddr() {
        return remoteAddr;
    }

    /**
     * 请求的 headers
     * @param request 请求
     */
    private void setHeaders(BufferedHttpServletRequestWrapper request) {
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            String headerValue = request.getHeader(headerName);
            this.headers.put(headerName.toLowerCase(), headerValue);
        }
    }

    /**
     * 返回指定 name 的 header
     * @param name header 名称
     * @return
     */
    public String getHeaders(String name) {
        return headers.get(name);
    }

    /**
     * 请求的 cookies
     * @param request 请求
     */
    private void setCookies(BufferedHttpServletRequestWrapper request) {
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                String name = cookie.getName();
                String value = cookie.getValue();
                this.cookies.put(name.toLowerCase(), value);
            }
        }
    }

    /**
     * 返回指定 name 的 cookie
     * @param name cookie 名称
     * @return
     */
    public String getCookies(String name) {
        return cookies.get(name);
    }

    /**
     * 请求的 url 中的参数（queryString 请求的查询字符串）
     * @param queryString 请求的查询字符串
     */
    private void setParams(String queryString) {
        if (StringUtils.isBlank(queryString)) {
            return;
        }
        try {
            String[] pairs = queryString.split("&");
            for (String pair : pairs) {
                String[] keyValue = pair.split("=");
                String key = "";
                String value = "";
                if (keyValue.length == 1) { // 没有值的参数，例如 &a=
                    key = URLDecoder.decode(keyValue[0], "UTF-8");
                } else if (keyValue.length == 2) { // 有值的参数，例如 &a=1 或 &=1
                    key = URLDecoder.decode(keyValue[0], "UTF-8");
                    value = URLDecoder.decode(keyValue[1], "UTF-8");
                }
                if (StringUtils.isBlank(key)) { // 如果 key 为空，说明这个参数是无效的，例如 &=1
                    continue;
                }
                if (this.params.get(key) == null) {
                    this.params.put(key, new ArrayList<>());
                }
                this.params.get(key).add(value);
            }
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 返回请求的 url 中的参数（queryString 请求的查询字符串）
     * @return
     */
    public Map<String, ArrayList<String>> getParams() {
        return params;
    }

    /**
     * 返回请求的 url 中的参数值
     * @param key 参数名
     * @return
     */
    public String[] getParamsValue2Array(String key) {
        if (params.get(key) == null) {
            throw new RuntimeException("参数不存在");
        } else {
            return params.get(key).toArray(new String[0]);
        }
    }

    /**
     * 返回请求的 url 中的参数值
     * @param key 参数名
     * @return
     */
    public ArrayList<String> getParamsValue2ArrayList(String key) {
        if (params.get(key) == null) {
            throw new RuntimeException("参数不存在");
        } else {
            return params.get(key);
        }
    }

    /**
     * 返回请求的 url 中的一个参数值
     * @param key 参数名
     * @return
     */
    public String getParamsValue2String(String key) {
        if (params.get(key) == null) {
            throw new RuntimeException("参数不存在");
        } else if (params.get(key).size() > 1) {
            throw new RuntimeException("参数值不唯一");
        } else if (params.get(key).size() == 1) {
            return params.get(key).get(0);
        } else {
            throw new RuntimeException("参数值不存在");
        }
    }

    /**
     * 设置 body
     * @param request
     */
    private void setBody(BufferedHttpServletRequestWrapper request) {
        try {
            if (this.contentType == MockContentType.FORM_DATA) {
                this.setFormDataBody(request.getParameterMap());
            } else if (this.contentType == MockContentType.X_WWW_FORM_URLENCODED) {
                this.setXWwwFormUrlencodedBody(request.getInputStream());
            } else if (this.contentType == MockContentType.JSON) {
                this.setJsonBody(request.getInputStream());
            } else if (this.contentType == MockContentType.TEXT) {
                this.setTextBody(request.getInputStream());
            } else if (this.contentType == MockContentType.JAVASCRIPT || this.contentType == MockContentType.HTML || this.contentType == MockContentType.XML) {
                this.setTextBody(request.getInputStream());
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 设置 FORM_DATA 类型请求的 body 中的所有参数
     * @param parameterMap 请求的参数
     */
    private void setFormDataBody(Map<String, String[]> parameterMap) {
        this.body = new HashMap<>();
        for (Map.Entry en : parameterMap.entrySet()) { // 如果 body 中的参数在 url 中没有，直接添加到 body 中
            if (params.get(en.getKey()) == null) {
                ArrayList<String> values = new ArrayList<>();
                for (String v : (String[]) en.getValue()) {
                    values.add(v);
                }
                ((Map<String, ArrayList<String>>) body).put((String) en.getKey(), values);
            } else { // 如果 body 中的参数在 url 中有，那么只添加 url 中没有的值
                ArrayList<String> urlParams = params.get(en.getKey());
                ArrayList<String> bodyValues = new ArrayList<>();
                for (String v : (String[]) en.getValue()) {
                    boolean exist = false;
                    for (String uv : urlParams) {
                        if (uv.equals(v)) {
                            exist = true;
                            break;
                        }
                    }
                    if (!exist) {
                        bodyValues.add(v);
                    }
                }
                if (bodyValues.size() > 0) {
                    ((Map<String, ArrayList<String>>) body).put((String) en.getKey(), bodyValues);
                }
            }
        }
    }

    /**
     * 设置 X_WWW_FORM_URLENCODED 类型请求的 body 中的所有参数
     * @param reader 请求的 reader
     * @throws IOException
     */
    private void setXWwwFormUrlencodedBody(ServletInputStream reader) throws IOException {
        String bodyStr = IOUtils.toString(reader, Charset.defaultCharset());
        Map<String, ArrayList<String>> tmpBody = new HashMap<>();

        String[] pairs = bodyStr.split("&");
        for (String pair : pairs) {
            String[] keyValue = pair.split("=");
            String key = "";
            String value = "";
            if (keyValue.length == 1) { // 没有值的参数，例如 &a=
                key = URLDecoder.decode(keyValue[0], "UTF-8");
            } else if (keyValue.length == 2) { // 有值的参数，例如 &a=1 或 &=1
                key = URLDecoder.decode(keyValue[0], "UTF-8");
                value = URLDecoder.decode(keyValue[1], "UTF-8");
            }
            if (StringUtils.isBlank(key)) { // 如果 key 为空，说明这个参数是无效的，例如 &=1
                continue;
            }
            if (tmpBody.get(key) == null) {
                tmpBody.put(key, new ArrayList<>());
            }
            tmpBody.get(key).add(value);
        }

        this.body = tmpBody;
    }

    /**
     * 设置 JSON 类型请求的 body 中的所有参数
     * @param reader 请求 body 的 reader
     * @throws IOException
     */
    private void setJsonBody(ServletInputStream reader) throws IOException {
        String bodyStr = IOUtils.toString(reader, Charset.defaultCharset());
        JSONValidator validator = JSONValidator.from(bodyStr);
        if (validator.getType() == JSONValidator.Type.Object) {
            this.body = JSON.parseObject(bodyStr);
        } else if (validator.getType() == JSONValidator.Type.Array) {
            this.body = JSON.parseArray(bodyStr);
        } else {
            throw new RuntimeException("JSON 格式错误");
        }
    }

    /**
     * 设置 TEXT 类型请求的 body
     * @param reader 请求 body 的 reader
     * @throws IOException
     */
    private void setTextBody(ServletInputStream reader) throws IOException {
        this.body = IOUtils.toString(reader, Charset.defaultCharset());
    }

    /**
     * 获取请求的 body
     * @return
     */
    public Object getBody() {
        return body;
    }

    /**
     * 获取请求的 body 中的参数值
     * @param key 参数名
     * @return
     */
    public String[] getBodyValue2Array(String key) {
        if (this.contentType == MockContentType.FORM_DATA || this.contentType == MockContentType.X_WWW_FORM_URLENCODED) {
            if (((Map<String, ArrayList<String>>) body).get(key) == null) {
                throw new RuntimeException("参数不存在");
            } else {
                return ((Map<String, ArrayList<String>>) body).get(key).toArray(new String[0]);
            }
        } else if (this.contentType == MockContentType.JSON) {
            JsonHelper jsonHelper = new JsonHelper(JSON.toJSONString(this.body));
            ArrayList<String> tmp = new ArrayList<>();
            if (jsonHelper == null) {
                throw new RuntimeException("参数值不存在");
            }
            Object value = jsonHelper.get(key.trim());
            if (value == null) {
                throw new RuntimeException("参数值不存在");
            } else if (value instanceof LinkedHashMap) {
                throw new RuntimeException("参数不是一个值，而是一个 JSONObject");
            } else if (value instanceof net.minidev.json.JSONArray) {
                ((JSONArray) value).forEach(obj -> {
                    if (obj instanceof LinkedHashMap) {
                        throw new RuntimeException("参数值中存在 JSONObject");
                    } else if (obj instanceof net.minidev.json.JSONArray) {
                        throw new RuntimeException("参数值中存在 JSONArray");
                    }
                    tmp.add(obj.toString());
                });
            } else {
                tmp.add(value.toString());
            }
            return tmp.toArray(new String[0]);
        } else if (this.contentType == MockContentType.TEXT) {
            if (StringUtils.isNotBlank(key)) {
                throw new RuntimeException(this.contentType.getName() + " 类型的请求 body 不能按参数名获取参数值");
            }
            return new String[]{(String) body};
        } else if (this.contentType == MockContentType.JAVASCRIPT || this.contentType == MockContentType.HTML || this.contentType == MockContentType.XML) {
            if (StringUtils.isNotBlank(key)) {
                throw new RuntimeException(this.contentType.getName() + " 类型的请求 body 不能按参数名获取参数值");
            }
            return new String[]{(String) body};
        }

        throw new RuntimeException("参数值不存在");
    }

    /**
     * 获取请求的 body 中的参数值
     * @param key 参数名
     * @return
     */
    public ArrayList<String> getBodyValue2ArrayList(String key) {
        if (this.contentType == MockContentType.FORM_DATA || this.contentType == MockContentType.X_WWW_FORM_URLENCODED) {
            if (((Map<String, ArrayList<String>>) body).get(key) == null) {
                throw new RuntimeException("参数不存在");
            } else {
                return ((Map<String, ArrayList<String>>) body).get(key);
            }
        } else if (this.contentType == MockContentType.JSON) {
            JsonHelper jsonHelper = new JsonHelper(JSON.toJSONString(this.body));
            ArrayList<String> tmp = new ArrayList<>();
            if (jsonHelper == null) {
                throw new RuntimeException("参数值不存在");
            }
            Object value = jsonHelper.get(key.trim());
            if (value == null) {
                throw new RuntimeException("参数值不存在");
            } else if (value instanceof LinkedHashMap) {
                throw new RuntimeException("参数不是一个值，而是一个 JSONObject");
            } else if (value instanceof net.minidev.json.JSONArray) {
                ((JSONArray) value).forEach(obj -> {
                    if (obj instanceof LinkedHashMap) {
                        throw new RuntimeException("参数值中存在 JSONObject");
                    } else if (obj instanceof net.minidev.json.JSONArray) {
                        throw new RuntimeException("参数值中存在 JSONArray");
                    }
                    tmp.add(obj.toString());
                });
            } else {
                tmp.add(value.toString());
            }
            return tmp;
        } else if (this.contentType == MockContentType.TEXT) {
            if (StringUtils.isNotBlank(key)) {
                throw new RuntimeException(this.contentType.getName() + " 类型的请求 body 不能按参数名获取参数值");
            }
            ArrayList<String> tmp = new ArrayList<>();
            tmp.add((String) body);
            return tmp;
        } else if (this.contentType == MockContentType.JAVASCRIPT || this.contentType == MockContentType.HTML || this.contentType == MockContentType.XML) {
            if (StringUtils.isNotBlank(key)) {
                throw new RuntimeException(this.contentType.getName() + " 类型的请求 body 不能按参数名获取参数值");
            }
            ArrayList<String> tmp = new ArrayList<>();
            tmp.add((String) body);
            return tmp;
        }

        throw new RuntimeException("参数值不存在");
    }

    /**
     * 返回请求的 body 中的一个参数值
     * @param key 参数名
     * @return
     */
    public String getBodyValue2String(String key) {
        if (this.contentType == MockContentType.FORM_DATA || this.contentType == MockContentType.X_WWW_FORM_URLENCODED) {
            if (((Map<String, ArrayList<String>>) body).get(key) == null) {
                throw new RuntimeException("参数不存在");
            } else if (((Map<String, ArrayList<String>>) body).get(key).size() > 1) {
                throw new RuntimeException("参数值不唯一");
            } else if (((Map<String, ArrayList<String>>) body).get(key).size() == 1) {
                return ((Map<String, ArrayList<String>>) body).get(key).get(0);
            } else {
                throw new RuntimeException("参数值不存在");
            }
        } else if (this.contentType == MockContentType.JSON) {
            JsonHelper jsonHelper = new JsonHelper(JSON.toJSONString(this.body));
            if (jsonHelper == null) {
                throw new RuntimeException("参数值不存在");
            }
            Object value = jsonHelper.get(key.trim());
            if (value == null) {
                throw new RuntimeException("参数值不存在");
            } else if (value instanceof LinkedHashMap) {
                throw new RuntimeException("参数不是一个值，而是一个 JSONObject");
            } else if (value instanceof net.minidev.json.JSONArray) {
                throw new RuntimeException("参数不是一个值，而是一个 JSONArray");
            }
            return value.toString();
        } else if (this.contentType == MockContentType.TEXT) {
            if (StringUtils.isNotBlank(key)) {
                throw new RuntimeException(this.contentType.getName() + " 类型的请求 body 不能按参数名获取参数值");
            }
            return (String) body;
        } else if (this.contentType == MockContentType.JAVASCRIPT || this.contentType == MockContentType.HTML || this.contentType == MockContentType.XML) {
            if (StringUtils.isNotBlank(key)) {
                throw new RuntimeException(this.contentType.getName() + " 类型的请求 body 不能按参数名获取参数值");
            }
            return (String) body;
        }

        throw new RuntimeException("参数值不存在");
    }

    @Override
    public String toString() {
        return "MockRequest{" +
                "contentType=" + contentType.getName() +
                ", Method=" + Method.getName() +
                ", url='" + url + '\'' +
                ", uri='" + uri + '\'' +
                ", scheme='" + scheme + '\'' +
                ", localPort=" + localPort +
                ", serverPort=" + serverPort +
                ", remotePort=" + remotePort +
                ", protocol='" + protocol + '\'' +
                ", host='" + host + '\'' +
                ", remoteHost='" + remoteHost + '\'' +
                ", remoteAddr='" + remoteAddr + '\'' +
                ", headers=" + headers +
                ", cookies=" + cookies +
                ", params=" + params +
                ", body=" + body +
                '}';
    }
}
